/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.dtv.lwuit;

import java.util.Vector;

import org.thenesis.lwuit.configuration.Configuration;
import org.thenesis.lwuit.configuration.ConfigurationProperties;
import org.thenesis.microbackend.ui.BackendEventListener;
import org.thenesis.microbackend.ui.UIBackend;
import org.thenesis.microbackend.ui.UIBackendFactory;
import org.thenesis.microbackend.ui.graphics.VirtualGraphics;
import org.thenesis.microbackend.ui.graphics.VirtualToolkit;

import com.sun.dtv.lwuit.animations.Animation;
import com.sun.dtv.lwuit.animations.Transition;
import com.sun.dtv.lwuit.geom.Rectangle;
import com.sun.dtv.lwuit.util.Log;

/**
 * This class implements many of the implementation details related to Display
 * that shouldn't be exposed by the API normally. It acts as the canvas, the
 * thread for the events and a command listener which are 3 unrelated tasks. The
 * purpose of using a single class for these 3 tasks is reduced class count and
 * thus reduced overhead.
 * 
 * @author Shai Almog
 */
public class VirtualImplementation implements Runnable {

    private static final int POINTER_PRESSED = 1;
    private static final int POINTER_RELEASED = 2;
    private static final int POINTER_DRAGGED = 3;
    private static final int KEY_PRESSED = 4;
    private static final int KEY_RELEASED = 5;
    private static final int SIZE_CHANGED = 7;

    public static final int SYSTEM_KEY_BACKSPACE = -2;
    public static final int SYSTEM_KEY_DELETE = -3;

    /**
     * The command used for accepting a text field change
     */
    private static Command CONFIRM_COMMAND;

    /**
     * Indicates the maximum drawing speed of no more than 10 frames per second
     * by defult (this can be increased or decreased) the advantage of limiting
     * framerate is to allow the CPU to perform other tasks besides drawing.
     * Notice that when no change is occuring on the screen no frame is drawn
     * and so a high/low FPS will have no effect then.
     */
    private int framerateLock = 30;

    /**
     * The command used for canceling a text field change
     */
    private static Command CANCEL_COMMAND;

    /**
     * The currently edited text box used to input into text field, this is a
     * MIDP implementation detail.
     */
    // private TextBox currentTextBox;
    /**
     * The currently edited text component that will be updated after the
     * dismissal of the text box
     */
    private Component currentTextComponent;

    private boolean flushGraphicsBug;
    private int transitionDelay;
    private Graphics wrapper;

    /**
     * Elements waiting for a paint
     */
    // private Vector paintQueue = new Vector();
    private Animation[] paintQueue = new Animation[50];
    private Animation[] paintQueueTemp = new Animation[50];
    private int paintQueueFill = 0;

    /**
     * Events to broadcast on the EDT
     */
    private Vector inputEvents = new Vector();

    // private static javax.microedition.lcdui.Display display;

    private boolean keyRepeatCharged;
    private long nextKeyRepeatEvent;
    private int keyRepeatValue;
    private int keyRepeatInitialIntervalTime = 800;
    private int keyRepeatNextIntervalTime = 10;
    private RunnableWrapper waitForEdit;
    private boolean block = false;

    private char lastKeyChar;
    private int lastKeyPressed;

    private EventMapper eventMapper = new GenericEventMapper();
    private VirtualBackendEventListener backendEventListener = new VirtualBackendEventListener();
    private VirtualToolkit virtualToolkit;

    void init(Object m) {

        //Copy LWUIT config in the MicroBackend config
        ConfigurationProperties properties = Configuration.getAllProperties();
        org.thenesis.microbackend.ui.Configuration microBackendConfig = new org.thenesis.microbackend.ui.Configuration();
        for (int i = 0; i < properties.size(); i++) {
            microBackendConfig.addParameter(properties.getKeyAt(i), properties.getValueAt(i));
        }

        virtualToolkit = VirtualToolkit.getToolkit();
        virtualToolkit.initialize(m, microBackendConfig, backendEventListener);

        wrapper = new Graphics();
        wrapper.setGraphics(getGraphics());
        setSoftKeyCodes();
    }

    /**
     * Plays sound for the dialog
     */
    void playDialogSound(final int type) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.playDialogSound(): not implemented yet");
        // TYPES[type - 1].playSound(display);
    }

    // void confirmControlsView() {
    // if(display == null){
    // throw new IllegalStateException(
    // "First call Display.setDisplay(javax.microedition.lcdui.Display d) method"
    // );
    // }
    // if(display.getCurrent() != this){
    // setCurrent(this);
    // }
    // }

    // private void setCurrent(Displayable d){
    // if(display == null){
    // throw new IllegalStateException(
    // "First call Display.setDisplay(javax.microedition.lcdui.Display d) method"
    // );
    // }
    // if(d instanceof Canvas){
    // ((Canvas)d).setFullScreenMode(true);
    // }
    // display.setCurrent(d);
    // }

    /**
     * Vibrates the device for the given length of time
     * 
     * @param duration
     *            length of time to vibrate
     */
    public void vibrate(int duration) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.vibrate()");
        // display.vibrate(duration);
    }

    /**
     * Flash the backlight of the device for the given length of time
     * 
     * @param duration
     *            length of time to flash the backlight
     */
    public void flashBacklight(int duration) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.flashBacklight()");
        // display.flashBacklight(duration);
    }

    /**
     * Indicates the maximum frames the API will try to draw every second by
     * defult this is set to 10. The advantage of limiting framerate is to allow
     * the CPU to perform other tasks besides drawing. Notice that when no
     * change is occuring on the screen no frame is drawn and so a high/low FPS
     * will have no effect then. 10FPS would be very reasonable for a business
     * application.
     * 
     * @param rate
     *            the frame rate
     */
    public void setFramerate(int rate) {
        framerateLock = 1000 / rate;
    }

    /**
     * Indicates the maximum frames the API will try to draw every second
     * 
     * @return the frame rate
     */
    public int getFrameRate() {
        return 1000 / framerateLock;
    }

    int getDisplayWidth() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.getDisplayWidth(): " + getWidth());
        return getWidth() - 1;
    }

    int getDisplayHeight() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.getDisplayWidth(): " + getHeight());
        return getHeight() - 1;
    }

    private void setSoftKeyCodes() {
        // Initialy set known key codes
        Form.leftSK = eventMapper.getSoftLeftKeyCode();
        Form.rightSK = eventMapper.getSoftRightKeyCode();
        Form.rightSK2 = eventMapper.getSoftRight2KeyCode();
        Form.clearSK = eventMapper.getClearKeyCode();
        Form.backSK = eventMapper.getBackKeyCode();

        try {
            // if the back key is assigned to a game action by mistake then
            // workaround it implicitly
            int game = getGameAction(Form.backSK);
            if (game == Display.GAME_UP || game == Display.GAME_DOWN || game == Display.GAME_RIGHT || game == Display.GAME_LEFT
                    || game == Display.GAME_FIRE) {
                Form.backSK = -50000;
            }
        } catch (Exception ok) {
        }

        try {
            // if the clear key is assigned to a game action by mistake then
            // workaround it implicitly
            int game = getGameAction(Form.clearSK);
            if (game == Display.GAME_UP || game == Display.GAME_DOWN || game == Display.GAME_RIGHT || game == Display.GAME_LEFT
                    || game == Display.GAME_FIRE) {
                Form.clearSK = -50000;
            }
        } catch (Exception ok) {
        }

    }

    public VirtualImplementation() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.<init>()");

        // super(false);
        // setTitle(null);
        // setFullScreenMode(true);

        // // disable the flashGraphics bug on Nokia phones
        // if(System.getProperty("microedition.platform").toUpperCase().indexOf(
        // "NOKIA") >= 0) {
        // flushGraphicsBug = false;
        //
        // // Symbian devices should yield a bit to let the paint thread
        // complete its work
        // // problem is we can't differentiate S40 from S60...
        // transitionDelay = 1;
        // } else {
        // flushGraphicsBug = true;
        // transitionDelay = -1;
        // }

        flushGraphicsBug = true;
        transitionDelay = -1;

        // TODO Remove
        // wrapper.setGraphics(getGraphics());
    }

    /**
     * Indicate to the implementation whether the flush graphics bug exists on
     * this device. By default the flushGraphics bug is set to "true" and only
     * disabled on handsets known 100% to be safe
     * 
     * @param flushGraphicsBug
     *            true if the bug exists on this device (the safe choice) false
     *            for slightly higher performance.
     */
    public void setFlashGraphicsBug(boolean flushGraphicsBug) {
        this.flushGraphicsBug = flushGraphicsBug;
    }

    /**
     * Indicates whether a delay should exist between calls to flush graphics
     * during transition. In some devices flushGraphics is asynchronious causing
     * it to be very slow with our background thread. The solution is to add a
     * short wait allowing the implementation time to paint the screen. This
     * value is set automatically by default but can be overriden for some
     * devices.
     * 
     * @param transitionDelay
     *            -1 for no delay otherwise delay in milliseconds
     */
    public void setTransitionYield(int transitionDelay) {
        this.transitionDelay = transitionDelay;
    }

    /**
     * Encapsulates the editing code which is specific to the platform, some
     * platforms would allow "in place editing" MIDP does not.
     * 
     * @param cmp
     *            the {@link TextArea} component
     */
    void editString(Component cmp, int maxSize, int constraint, String text) {

        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.editString()");

        EditForm editForm = new EditForm(this, Display.getInstance().getCurrent(), (TextArea) cmp);
        Display.getInstance().setCurrent(editForm);
    }

    /**
     * Returns the video control for the media player
     * 
     * @param player
     *            the media player
     * @return the video control for the media player
     */
    Object getVideoControl(Object player) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.getVideoControl()");
        return null;

        // VideoControl vidc =
        // (VideoControl)((Player)player).getControl("VideoControl");
        // vidc.initDisplayMode(vidc.USE_DIRECT_VIDEO, this);
        // return vidc;
    }

    /**
     * Return the number of alpha levels supported by the implementation.
     * 
     * @return the number of alpha levels supported by the implementation
     */
    public int numAlphaLevels() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.numAlphaLevels()");
        return 0;
        // return display.numAlphaLevels();
    }

    /**
     * Returns the number of colors applicable on the device, note that the API
     * does not support gray scale devices.
     * 
     * @return the number of colors applicable on the device
     */
    public int numColors() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.numColors()");
        return 1 << 24;
        // return display.numColors();
    }

    protected void showNotify() {
        Form current = Display.getInstance().getCurrentInternal();
        if (current != null) {
            current.showNotify();
        }
    }

    protected void hideNotify() {
        Form current = Display.getInstance().getCurrentInternal();
        if (current != null) {
            current.hideNotify();
        }
    }

    /**
     * Invoked by the EDT to paint the dirty regions
     */
    void paintDirty() {
        int size = 0;
        synchronized (Display.lock) {
            size = paintQueueFill;
            Animation[] array = paintQueue;
            paintQueue = paintQueueTemp;
            paintQueueTemp = array;
            paintQueueFill = 0;
        }
        if (size > 0) {
            wrapper.setGraphics(getGraphics());
            int topX = getWidth();
            int topY = getHeight();
            int bottomX = 0;
            int bottomY = 0;
            for (int iter = 0; iter < size; iter++) {
                Animation ani = paintQueueTemp[iter];
                paintQueueTemp[iter] = null;
                wrapper.translate(-wrapper.getTranslateX(), -wrapper.getTranslateY());
                wrapper.setClip(0, 0, getWidth(), getHeight());
                if (ani instanceof Component) {
                    Component cmp = (Component) ani;
                    System.out.println(cmp.getUIID() + " <- imprimido");
                    Rectangle dirty = cmp.getDirtyRegion();
                    //Rectangle dirty = new Rectangle(0, 0, cmp.getPreferredSize()) ;
                    if (dirty != null) {
                        System.out.println(cmp.getUIID() + " <- sujo");
                        wrapper.setClip(dirty.getX(), dirty.getY(), dirty.getSize().getWidth(), dirty.getSize().getHeight());
                        cmp.setDirtyRegion(null);
                    }

                    cmp.paintComponent(wrapper);
                    int cmpAbsX = cmp.getAbsoluteX() + cmp.getScrollX();
                    topX = Math.min(cmpAbsX, topX);
                    bottomX = Math.max(cmpAbsX + cmp.getWidth(), bottomX);
                    int cmpAbsY = cmp.getAbsoluteY() + cmp.getScrollY();
                    topY = Math.min(cmpAbsY, topY);
                    bottomY = Math.max(cmpAbsY + cmp.getHeight(), bottomY);
                } else {
                    bottomX = getWidth();
                    bottomY = getHeight();
                    topX = 0;
                    topY = 0;
                    ani.paint(wrapper);
                }
            }

            // disable flush graphics bug when media is playing to prevent the
            // double buffer
            // from clearing the media and producing flickering
            Form current = Display.getInstance().getCurrentInternal();
            if (!flushGraphicsBug || (current != null && current.hasMedia())) {
                flushGraphics(topX, topY, bottomX - topX, bottomY - topY);
            } else {
                flushGraphics();
            }
        }
    }

    public void repaint(Animation cmp) {
        synchronized (Display.lock) {
            for (int iter = 0; iter < paintQueueFill; iter++) {
                if (paintQueue[iter] == cmp) {
                    return;
                }
            }
            // overcrowding the queue don't try to grow the array!
            if (paintQueueFill >= paintQueue.length) {
                System.out.println("Warning paint queue size exceeded, please watch the amount of repaint calls");
                return;
            }
            
            paintQueue[paintQueueFill] = cmp;
            paintQueueFill++;
            Display.lock.notify();
        }
    }

    private void addInputEvent(int[] ev) {
        synchronized (Display.lock) {
            inputEvents.addElement(ev);
            Display.lock.notify();
        }
    }

    private int[] createSizeChangedEvent(int w, int h) {
        return new int[] { SIZE_CHANGED, w, h };
    }

    /**
     * Creates a pointer event with the following properties
     */
    private int[] createPointerEvent(int x, int y, boolean pressed) {
        if (pressed) {
            return new int[] { POINTER_PRESSED, x, y };
        } else {
            return new int[] { POINTER_RELEASED, x, y };
        }
    }

    /**
     * Creates a pointer event with the following properties
     */
    private int[] createPointerDragEvent(int x, int y) {
        return new int[] { POINTER_DRAGGED, x, y };
    }

    private int[] createKeyEvent(int keyCode, boolean pressed) {
        if (pressed) {
            return new int[] { KEY_PRESSED, keyCode };
        } else {
            return new int[] { KEY_RELEASED, keyCode };
        }
    }

    protected void sizeChanged(int w, int h) {
        Form current = Display.getInstance().getCurrentInternal();
        if (current == null) {
            return;
        }
        if (w == getDisplayWidth() && h == getDisplayHeight()) {
            return;
        }

        addInputEvent(createSizeChangedEvent(w, h));
    }

    /**
     * Used by the flush functionality which doesn't care much about component
     * animations
     */
    public boolean shouldEDTSleepNoFormAnimation() {
        Display d = Display.getInstance();
        Vector animationQueue = d.getAnimationQueue();
        synchronized (Display.lock) {
            return (animationQueue == null || animationQueue.size() == 0) && inputEvents.size() == 0 && paintQueueFill == 0
                    && d.hasNoSerialCallsPending();
        }
    }

    /**
     * Returns true for a case where the EDT has nothing at all to do
     */
    public boolean shouldEDTSleep() {
        Display d = Display.getInstance();
        Vector animationQueue = d.getAnimationQueue();
        Form current = d.getCurrentInternal();
        return (current == null || (!current.hasAnimations())) && (animationQueue == null || animationQueue.size() == 0)
                && inputEvents.size() == 0 && paintQueueFill == 0 && d.hasNoSerialCallsPending();
    }

    /**
     * Run is used both to invoke the main EDT loop
     */
    public void run() {
        mainEDTLoop();
        //TODO: Remove
        System.exit(0);
    }

    public static boolean PARAR_DE_RODAR = false;

    /**
     * This method represents the event thread for the UI library on which all
     * events are carried out. It differs from the MIDP event thread to prevent
     * blocking of actual input and drawing operations. This also enables
     * functionality such as "true" modal dialogs etc...
     */
    private void mainEDTLoop() {
        Vector animationQueue = Display.getInstance().getAnimationQueue();

        try {
            synchronized (Display.lock) {
                // when there is no current form the EDT is useful only
                // for features such as call serially
                while (Display.getInstance().getCurrentInternal() == null) {
                    if (shouldEDTSleep()) {
                        Display.lock.wait();
                    }

                    // paint transition or intro animations and don't do
                    // anything else if such
                    // animations are in progress...
                    if (animationQueue != null && animationQueue.size() > 0) {
                        paintTransitionAnimation();
                        continue;
                    }
                    processSerialCalls();
                }
            }
        } catch (Throwable err) {
            err.printStackTrace();
            Dialog.show("Error", "An internal application error occured: " + err.toString(), "OK", null);
        }

        //ADD BY CEBOLA
        while (!PARAR_DE_RODAR) {
            try {
                // wait indefinetly but no more than the framerate if
                // there are no animations... If animations exist then
                // only wait for the framerate
                if (shouldEDTSleep()) {
                    synchronized (Display.lock) {
                        Display.lock.wait();
                    }
                }

                edtLoopImpl();
            } catch (Throwable err) {
                err.printStackTrace();
                Dialog.show("Error", "An internal application error occured: " + err.toString(), "OK", null);
            }
        }
    }

    /**
     * Implementation of the event dispatch loop content
     */
    public void edtLoopImpl() {
        long time = System.currentTimeMillis();
        try {
            Vector animationQueue = Display.getInstance().getAnimationQueue();
            // transitions shouldn't be bound by framerate
            if (animationQueue == null || animationQueue.size() == 0) {
                // prevents us from waking up the EDT too much and
                // thus exhausting the systems resources. The + 1
                // prevents us from ever waiting 0 milliseconds which
                // is the same as waiting with no time limit
                long currentTime = System.currentTimeMillis() + 1;
                while (currentTime - time < framerateLock) {
                    synchronized (Display.lock) {
                        Display.lock.wait(Math.min(1, framerateLock - (currentTime - time)));
                    }
                    currentTime = System.currentTimeMillis() + 1;
                }
            } else {
                // paint transition or intro animations and don't do anything
                // else if such
                // animations are in progress...
                paintTransitionAnimation();
                return;
            }
        } catch (InterruptedException ignor) {
        }

        while (inputEvents.size() > 0 && !block) {
            int[] i = (int[]) inputEvents.elementAt(0);
            inputEvents.removeElementAt(0);
            handleEvent(i);
        }

        // draw the animations
        Display.getInstance().getCurrentInternal().repaintAnimations();
        paintDirty();

        // check key repeat events
        if (!block && keyRepeatCharged && nextKeyRepeatEvent <= System.currentTimeMillis()) {
            Display.getInstance().getCurrentInternal().keyRepeated(keyRepeatValue);
            nextKeyRepeatEvent = System.currentTimeMillis() + keyRepeatNextIntervalTime;
        }

        processSerialCalls();
    }

    /**
     * Restores the menu in the given form
     */
    private void restoreMenu(Form f) {
        if (f != null) {
            f.restoreMenu();
        }
    }

    private void paintTransitionAnimation() {
        Vector animationQueue = Display.getInstance().getAnimationQueue();
        Animation ani = (Animation) animationQueue.elementAt(0);
        if (!ani.animate()) {
            animationQueue.removeElementAt(0);
            if (ani instanceof Transition) {
                Form current = Display.getInstance().getCurrentInternal();
                Form source = (Form) ((Transition) ani).getSource();
                restoreMenu(source);
                Form f = (Form) ((Transition) ani).getDestination();
                restoreMenu(f);
                if (source == null || source == current || source == Display.getInstance().getCurrent()) {
                    Display.getInstance().setCurrentForm(f);
                }
                ((Transition) ani).cleanup();
                if (animationQueue.size() > 0) {
                    ani = (Animation) animationQueue.elementAt(0);
                    if (ani instanceof Transition) {
                        ((Transition) ani).initTransition();
                    }
                }
                return;
            }
        }
        ani.paint(wrapper);
        flushGraphics();

        if (transitionDelay > 0) {
            // yield for a fraction, some devices don't "properly" implement
            // flush and so require the painting thread to get CPU too.
            try {
                synchronized (Display.lock) {
                    Display.lock.wait(transitionDelay);
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Used by the EDT to process all the calls submitted via call serially
     */
    private void processSerialCalls() {
        Display.getInstance().processSerialCalls();
    }

    /**
     * Invoked on the EDT to propagate the event
     */
    private void handleEvent(int[] ev) {
        switch (ev[0]) {
        case KEY_PRESSED:
            Display.getInstance().getCurrentInternal().keyPressed(ev[1]);
            break;
        case KEY_RELEASED:
            Display.getInstance().getCurrentInternal().keyReleased(ev[1]);
            break;
        case POINTER_PRESSED:
            Display.getInstance().getCurrentInternal().pointerPressed(ev[1], ev[2]);
            break;
        case POINTER_RELEASED:
            Display.getInstance().getCurrentInternal().pointerReleased(ev[1], ev[2]);
            break;
        case POINTER_DRAGGED:
            Display.getInstance().getCurrentInternal().pointerDragged(ev[1], ev[2]);
            break;
        case SIZE_CHANGED:
            Display.getInstance().getCurrentInternal().sizeChanged(ev[1], ev[2]);
            break;
        }
    }

    void blockEvents(boolean block) {
        this.block = block;
    }

    // /**
    // * This method is used for text input purposes only
    // */
    // public void commandAction(Command c, Displayable d) {
    // if (d == currentTextBox) {
    // if (c == CONFIRM_COMMAND) {
    // // confirm
    // String text = currentTextBox.getString();
    // currentTextComponent.onEditComplete(text);
    // currentTextComponent.fireActionEvent();
    // }
    // currentTextBox = null;
    //Display.getInstance().setCurrentForm(currentTextComponent.getComponentForm
    // ());
    // waitForEdit.setDone(true);
    // }
    // }
    //
    public void saveTextBox() {

        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.saveTextBox(): not implemented yet");

        // String text = currentTextBox.getString();
        // currentTextComponent.onEditComplete(text);
        // currentTextComponent.fireActionEvent();
        // currentTextBox = null;
        // waitForEdit.setDone(true);
    }

    /* TODO Implement me */

    private int getWidth() {
        // TODO
        //return 210;
        return virtualToolkit.getWidth();
    }

    private int getHeight() {
        // TODO
        //return 640;
        return virtualToolkit.getHeight();
    }

    private void flushGraphics() {
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.flushGraphics()");
        virtualToolkit.flushGraphics(0, 0, getWidth(), getHeight());
    }

    private void flushGraphics(int topX, int topY, int i, int j) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.flushGraphics(int topX, int topY, int i, int j): not implemented yet");
    }

    private VirtualGraphics getGraphics() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.getGraphics()");
        VirtualGraphics g = virtualToolkit.getRootGraphics();
        return g;
    }

    public boolean hasPointerEvents() {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.hasPointerEvents(): not implemented yet");
        return true;
    }

    public int getGameAction(int keyCode) {
        // TODO
        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] VirtualImplementation.getGameAction()");
        return eventMapper.getGameAction(keyCode);
    }

    char getLastKeyChar() {
        return lastKeyChar;
    }

    EventMapper getEventMapper() {
        return eventMapper;
    }
    

    VirtualToolkit getVirtualToolkit() {
        return virtualToolkit;
    }


    /**
     * Listens events coming from the UIBackend and send it to the event queue
     */
    private class VirtualBackendEventListener implements BackendEventListener {

        private boolean dragEnabled = false;

        public VirtualBackendEventListener() {
        }

        public void keyPressed(int keyCode, char c, int modifiers) {

            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.keyPressed(): keyCode=" + keyCode + " c=" + c);

            Form current = Display.getInstance().getCurrentInternal();
            if (current == null) {
                return;
            }
            addInputEvent(createKeyEvent(keyCode, true));
            keyRepeatCharged = true;
            keyRepeatValue = keyCode;
            nextKeyRepeatEvent = System.currentTimeMillis() + keyRepeatInitialIntervalTime;
            lastKeyPressed = keyCode;
            lastKeyChar = c;
        }

        public void keyReleased(int keyCode, char c, int modifiers) {

            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.keyReleased(): keyCode=" + keyCode + " c=" + c);

            keyRepeatCharged = false;
            Form current = Display.getInstance().getCurrentInternal();
            if (current == null) {
                return;
            }
            // this can happen when traversing from the native form to the
            // current
            // form
            // caused by a keypress
            if (keyCode != lastKeyPressed) {
                return;
            }
            addInputEvent(createKeyEvent(keyCode, false));
        }

        public void mouseMoved(int x, int y, int modifiers) {
            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.mouseMoved(): x=" + x + " y=" + y + " drag enabled ? "
                        + dragEnabled);

            if (dragEnabled) {
                Form current = Display.getInstance().getCurrentInternal();
                if (current == null) {
                    return;
                }
                addInputEvent(createPointerDragEvent(x, y));
            }

        }

        public void mousePressed(int x, int y, int modifiers) {
            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.mousePressed(): x=" + x + " y=" + y);

            dragEnabled = true;
            Form current = Display.getInstance().getCurrentInternal();
            if (current == null) {
                return;
            }
            addInputEvent(createPointerEvent(x, y, true));
        }

        public void mouseReleased(int x, int y, int modifiers) {
            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.mouseReleased(): x=" + x + " y=" + y);

            dragEnabled = false;
            Form current = Display.getInstance().getCurrentInternal();
            if (current == null) {
                return;
            }
            addInputEvent(createPointerEvent(x, y, false));
        }

        public void windowClosed() {
            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualBackendEventListener.windowClosed(): Window delete event received");

        }

    }

}
